Engine.Render = function(width,height) {
    this.scene = new Engine.Scene();
    this.width = width;
    this.height = height;
    this.zbuffer = [];
}

Engine.Render.prototype = (function(){
    return {
        begin:function(){
            var size = this.width * this.height;
            this.zbuffer = [];
            for (var i = 0; i < size; i++) {
                this.zbuffer[i] = {
                    z: Number.MAX_VALUE,
                    r: 0xff,
                    g: 0xff,
                    b: 0xff,
        			a: 0xff
                };
            }
            this.scene.begin(this.width, this.height);
        },
        
        putPoint : function(x, y, r, g, b, a, z)
         {
            var index = x + y * this.width;
            this.zbuffer[index] = {
                    r: r,
                    g: g,
                    b: b,
                    a: a,
                    z: z
            };
        },
        zbufferTest : function(x, y, z)
         {
            if (x < 0 || y < 0 || x >= this.width || y >= this.height)
                return false;
            var index = x + y * this.width;
            if (z <= this.zbuffer[index].z && z >= 0) {
                return true;
            }
            else
            {
                return false;
            }
        },
        draw : function(){
            for(var i = 0; i <  this.scene.gameObjects.length; i++){
                this.scene.gameObjects[i].begin(this.width,this.height,this.scene.camera,this.scene.staticScene);
                this.drawGameObject(this.scene.gameObjects[i]);
            }
        },
        drawGameObject : function(go){
            if(go.shader.type == Engine.Reflection && !go.shader.cubeTexture){
                var center = go.transform.m2w().multiplyVector(go.center());
                center = new Engine.Vector3(center.x / center.w,center.y / center.w,center.z / center.w);
                var rd = new Engine.Render(this.width,this.height);
                rd.scene.staticScene = true;
                for(var i = 0; i <  this.scene.gameObjects.length; i++){
                    if(go.id != this.scene.gameObjects[i].id)
                        rd.scene.addGameObject(this.scene.gameObjects[i]);
                }

                var cube = {};
                var Z = center.minus(this.scene.camera.position).normalize();
                var X = this.scene.camera.worldup.cross(Z).normalize();
                var Y = Z.cross(X).normalize();

                var camera = new Engine.Camera(center,center.plus(X),Y, 90);
                rd.scene.camera = camera;
                rd.begin();
                rd.draw();
                cube["right"] = new Engine.Texture(this.width,this.height,rd.zbuffer);
                
                rd.scene.camera.lookat = center.plus(X.multiply(-1));
                rd.begin();
                rd.draw();
                cube["left"] = new Engine.Texture(this.width,this.height,rd.zbuffer);
            	
                rd.scene.camera.lookat = center.plus(Z);
                rd.begin();
                rd.draw();
                cube["front"] = new Engine.Texture(this.width,this.height,rd.zbuffer);
            	
                rd.scene.camera.lookat = center.plus(Z.multiply(-1));
                rd.begin();
                rd.draw();
                cube["back"] = new Engine.Texture(this.width,this.height,rd.zbuffer);
            	
                rd.scene.camera.worldup = Z.multiply(-1);
                rd.scene.camera.lookat = center.plus(Y);
                rd.begin();
                rd.draw();
                cube["top"] = new Engine.Texture(this.width,this.height,rd.zbuffer);
            	
            	rd.scene.camera.worldup = Z;
                rd.scene.camera.lookat = center.plus(Y.multiply(-1));
                rd.begin();
                rd.draw();
                cube["bottom"] = new Engine.Texture(this.width,this.height,rd.zbuffer);            	
            	
                go.shader.cubeTexture = cube;
            }
			
		
            for(var i=0; i< go.triangles.length;i++){
                this.drawTriangle(go.triangles[i],go.m2s,go.m2c,go.m2cn,go.shader);
            }
        },
        drawTriangle : function(triangle,m2s,m2c,m2cn,shader){

            triangle.begin(m2s,m2c,m2cn);
            if(triangle.cv[0].z<0 || triangle.cv[1].z<0 || triangle.cv[2].z<0)
                return;
            for (x = Math.max(0,triangle.left); x <= triangle.right && x <=  this.width; x++) {
                for (y = Math.max(0,triangle.top); y <= triangle.bottom && y <= this.height; y++) {
                    if (triangle.isInside(x, y)) {
        				var z = triangle.interpolateZ(x, y);
                        var v = this.screen2camera(x,y,z);
        			    if(v.z > Engine.Near && v.z < Engine.Far && this.zbufferTest(x,y,v.z)){
                            var coe = triangle.coecients3D(v,triangle.cv[0],triangle.cv[1],triangle.cv[2]);

                        	var uv = {
                        		u : triangle.uv[0].u * coe.a + triangle.uv[1].u * coe.b + triangle.uv[2].u * coe.r,
                        		v : triangle.uv[0].v * coe.a + triangle.uv[1].v * coe.b + triangle.uv[2].v * coe.r
                        	};
                            var n = new Engine.Vector3(
                        	    triangle.cn[0].x * coe.a + triangle.cn[1].x * coe.b + triangle.cn[2].x * coe.r,
                        	    triangle.cn[0].y * coe.a + triangle.cn[1].y * coe.b + triangle.cn[2].y * coe.r,
                        	    triangle.cn[0].z * coe.a + triangle.cn[1].z * coe.b + triangle.cn[2].z * coe.r
                        	);
            		        var c = shader.color(v,n,uv,this.scene);
            				this.putPoint(x, y, c.r, c.g, c.b, 0xff,v.z);
    			        }
                    }
                }
            }
        },
        flush : function(device) {
            for (var e = 0 ; e < this.zbuffer.length ; e++)
            {
                var x = e % this.width;
                var y = Math.floor(e / this.width);
                device.setPixel(x, y, this.zbuffer[e].r, this.zbuffer[e].g, this.zbuffer[e].b, this.zbuffer[e].a);
            }
            device.flush();
        },
        
        flush2file : function(builder) {
            builder.append("P6 " + this.width + " " + this.height + " 255\n");
            var array = new Uint8Array(this.zbuffer.length * 3);

            for (var i = 0; i < this.zbuffer.length; i++)
            {
                var index = i * 3;
                array[index] = this.zbuffer[i].r;
                array[index + 1] = this.zbuffer[i].g;
                array[index + 2] = this.zbuffer[i].b;

                if(this.zbuffer[i].g > 255 || this.zbuffer[i].r > 255 || this.zbuffer[i].b > 255)
                    console.log("bad");
            }
            builder.append(array.buffer);
        },
        
        depthMap: function(device){
            var threshold = 1;
            var pixel = 1;
            for (var e = 0 ; e < this.zbuffer.length ; e++)
            {
                var x = e % this.width;
                var y = Math.floor(e / this.width);
                if((e-pixel >= 0 && Math.abs(this.zbuffer[e-pixel].z - this.zbuffer[e].z) > threshold) ||
                (e+pixel < this.zbuffer.length && Math.abs(this.zbuffer[e+pixel].z - this.zbuffer[e].z) > threshold) ||
                (e - pixel * this.width >= 0 && Math.abs(this.zbuffer[e - pixel * this.width].z - this.zbuffer[e].z) > threshold) ||
                (e + pixel * this.width < this.zbuffer.length && Math.abs(this.zbuffer[e + pixel * this.width].z - this.zbuffer[e].z) > threshold)){
                    device.setPixel(x, y, 0, 0, 0, 255);
					this.zbuffer[e].r = 0;
					this.zbuffer[e].g = 0;
					this.zbuffer[e].b = 0;
                }
                else{
					var toonColor = getToonColor(this.zbuffer[e].r,this.zbuffer[e].g,this.zbuffer[e].b,5,1);
					this.zbuffer[e].r = toonColor.r;
					this.zbuffer[e].g = toonColor.g;
					this.zbuffer[e].b = toonColor.b;
                    device.setPixel(x, y, toonColor.r, toonColor.g, toonColor.b, 255);
                }
            }
            device.flush();
        },




        screen2camera : function(x,y,z){
            var temp = z/(Engine.MaxZ-z);
            var w = this.width/2;
            var h = this.height/2;
            return new Engine.Vector3(
                (x - w)/w * (temp + 1),
                (this.height - y - h)/h * (temp + 1),
                temp * this.scene.camera.d()
            );
        }
    };
})();

function getToonColor(r,g,b,colorNum,mode)
{
	var rColor;
	var gColor;
	var bColor;
	
	var hue = getHue(r,g,b);
	
	
	
	
	
	if(mode == 0)
	{
		var cValue = (0.21*r + 0.71*g + 0.08*b)/255 * colorNum;
		cValue = Math.round(cValue);
		cValue = Math.round(cValue/colorNum * 255);
		rColor = cValue;
		gColor = cValue;
		bColor = cValue;
	}else if (mode == 1)
	{
		var rColor = r/255*colorNum;
		rColor = Math.round(rColor);
		rColor = Math.round(rColor/colorNum*255);
		
		var gColor = g/255*colorNum;
		gColor = Math.round(gColor);
		gColor = Math.round(gColor/colorNum*255);
	
		var bColor = b/255*colorNum;	
		bColor = Math.round(bColor);
		bColor = Math.round(bColor/colorNum*255);
		 
	}else if (mode == 2)
	{
		var blue = {r:3,g:99,b:149};
		var green = {r:39,g:127,b:66};
		var yellow = {r:227,g:200,b:83};
		var red = {r:218,g:80,b:41};
		var pink = {r:229,g:177,b:156};
		var black = {r:5,g:5,b:5};
		var white = {r:255,g:255,b:255};
		var color = [];
		color.push(blue);
		color.push(green);
		color.push(yellow);
		color.push(red);
		color.push(pink);
		color.push(black);
		color.push(white);
		var distance = Number.MAX_VALUE;
		var chooseNum;
		for(var i=0;i<color.length;i++)
		{
			//var thisDistance = (Math.pow(color[i].r - r,2)*0.21 + Math.pow(color[i].g - g,2)*0.71 + Math.pow(color[i].b - b,2)*0.08);
			
			var thisHue = getHue(color[i].r,color[i].g,color[i].b);
			var thisDistance = Math.abs(hue-thisHue);
			
			if(thisDistance < distance)
			{
				distance = thisDistance;
				chooseNum = i;
			}
		}
		
	
		
		return color[chooseNum];
		
	}
	
	return {r:rColor,g:gColor,b:bColor};
	
}

function getHue(r,g,b)
{
	//var Hue = Math.atan2(Math.sqrt(3)*(g-b),2*r-g-b)*180/Math.PI;
	var Hue;
	if(r==g && g == b)
	{
		Hue = 0;
	}else
	{
	if(r >= g && g >= b)
	{
		Hue = 60 * (g-b)/(r-b);
	}else if(g > r && r >= b)
	{
		Hue = 60 * (2 - (r-b)/(g-b));
	}

	else if(g >= b && b > r)
	{
		Hue = 60 * (2 + (b-r)/(g-r));
	}
	else if(b > g && g > r)
	{
		Hue = 60 * (4 - (g-r)/(b-r));
	}else if(b > r && r >= g)
	{
		Hue = 60 * (4 + (r-g)/(b-g));
	}else if(r >= b && b > g)
	{
		Hue = 60 * (6 - (b-g)/(r-g));
	}
	}
	return Hue;
}
